package model.minimax;

import java.util.ArrayList;

import model.GamePiece;
import model.LadderGameHelper;
import model.LadderGridHelper;
import model.Slot;

/**
 * This class basically implements the minimax algorithm.
 * It adds the children (as grid conifguration) to each of the nodes.
 * It returns the min and max value depending on the heuristic value.
 * It also alpha-beta pruning.
 * The heuristic value is received by calling Evaluator class' methods.
 * 
 */
public class StateNode
{
    /**
     * A list of the players' position and pieces 
     * describing this node's game state. 
     */
    private ArrayList<ArrayList<GamePiece>> grid;

    /**
     * The next piece to be played
     */
    private GamePiece nextTurnPiece;

    /**
     * The different moves to consider from this StateNode.
     */
    private ArrayList<MoveEdge> moves;
    
    /**
     * The Parent Edge of this node
     */
    private MoveEdge parentEdge;

    /**
     * This Node's heuristic value
     */
    private Integer heuristicValue;

    public StateNode(ArrayList<ArrayList<GamePiece>> grid, GamePiece nextTurnPiece)
    {
        super();
        this.grid = grid;
        this.nextTurnPiece = nextTurnPiece;
        this.moves = new ArrayList<MoveEdge>();

        this.parentEdge = null;
        this.heuristicValue = null;
    }

    /**
     * Lazy constructor, builds the grid only when getGrid() is called.
     * 
     * @param parentEdge
     * @param nextTurnPiece
     */
    public StateNode(MoveEdge parentEdge, GamePiece nextTurnPiece)
    {
        super();
        this.parentEdge = parentEdge;
        this.nextTurnPiece = nextTurnPiece;
        this.moves = new ArrayList<MoveEdge>();

        this.grid = null;
        this.heuristicValue = null;
    }

    /**
     * Add a child to this node.
     * 
     * @param move the slot position.
     */
    public void addChild(Slot move)
    {
        try
        {
            MoveEdge newMove = new MoveEdge(move, this);
            StateNode child = new StateNode(newMove, LadderGameHelper.getOpponentGamePiece(nextTurnPiece));
            newMove.setTargetNode(child);

            int childHeuristicValue = Evaluator.evaluate(this.getGrid());
            if(childHeuristicValue >= Evaluator.INFINITY
               || childHeuristicValue <= -Evaluator.INFINITY)
            {
                // don't add the child.
            }
            else
            {
                this.moves.add(newMove);
            }
        }
        catch (Exception e)
        {
            // do nothing
            e.printStackTrace();
        }
    }

    public ArrayList<StateNode> getChildren()
    {
        ArrayList<StateNode> children = new ArrayList<StateNode>(this.moves.size());

        for (MoveEdge move : this.moves)
        {
            children.add(move.getTargetNode());
        }

        return children;
    }

    /**
     * Returns the heuristic Value of the stateNode.
     * 
     * @param grid
     * @return
     */
    public int getHeuristicValue()
    {
        return this.alphabeta(-Evaluator.INFINITY, Evaluator.INFINITY);
    }

    private int alphabeta(int alpha, int beta)
    {
        if(this.heuristicValue == null){
            // Calculate Heuristic Value using alpha-beta pruning
            if (this.isLeaf())
            {
                this.heuristicValue = Evaluator.evaluate(this.getGrid());
            }
            else if (this.nextTurnPiece == GamePiece.WHITE)
            {
                // Max Player
                for (MoveEdge move : this.moves)
                {
                    StateNode child = move.getTargetNode();
                    alpha = Math.max(alpha, child.alphabeta(alpha, beta));
                    if (beta <= alpha)
                    {
                        break; // Beta Cut-off
                    }
                }
                this.heuristicValue = alpha;
            }
            else
            {
                // Min Player
                for (MoveEdge move : this.moves)
                {
                    StateNode child = move.getTargetNode();
                    beta = Math.min(beta, child.alphabeta(alpha, beta));
                    if (beta <= alpha)
                    {
                        break; // Alpha Cut-off
                    }
                }
                this.heuristicValue = beta;
            }
        }
        return this.heuristicValue.intValue();
    }

    public void resetHeuristicValue()
    {
        for(MoveEdge edge : this.moves)
        {
            edge.getTargetNode().resetHeuristicValue();
        }        
        this.heuristicValue = null;
    }
    
    /**
     * Returns the edge from the root whose targetNode has the max heuristic
     * value, null if the root does not have any children.
     * 
     * @return
     */
    public MoveEdge getMaxMove()
    {
        MoveEdge max = null;

        for (MoveEdge edge : this.moves)
        {
            if (max == null || max.getTargetNode().getHeuristicValue() < edge.getTargetNode().getHeuristicValue())
            {
                max = edge;
            }
        }

        return max;
    }

    /**
     * Returns the edge from the root whose targetNode has the min heuristic
     * value, null if the root does not have any children.
     * 
     * @return
     */
    public MoveEdge getMinMove()
    {
        MoveEdge min = null;

        for (MoveEdge edge : this.moves)
        {
            if (min == null || min.getTargetNode().getHeuristicValue() > edge.getTargetNode().getHeuristicValue())
            {
                min = edge;
            }
        }

        return min;
    }

    public ArrayList<ArrayList<GamePiece>> getGrid()
    {
        if (this.grid == null)
        {
            // building grid
            ArrayList<ArrayList<GamePiece>> childGrid = LadderGridHelper.deepCopy(this.parentEdge.getSourceNode().getGrid());
            Slot move = this.parentEdge.getMove();
            childGrid.get(move.getRowIndex()).set(move.getColumnIndex(), this.parentEdge.getSourceNode().getNextTurnPiece());

            this.grid = childGrid;
        }
        return grid;
    }

    public GamePiece getNextTurnPiece()
    {
        return nextTurnPiece;
    }

    public ArrayList<MoveEdge> getMoves()
    {
        return moves;
    }

    private boolean isLeaf()
    {
        return this.moves.isEmpty();
    }
}
